package resml

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// 原始文件的存储
type OsFileRawStorage struct {
	root   string // 文件根目录
	meta   string // 元数据根目录
	etager ETager
	append bool // 是否以追加的方式保存文件
	states *StorageStates
}

// 创建一个原始文件的存储
func NewOsFileRawStorage(root string, etager ETager, append bool) (s *OsFileRawStorage, err error) {
	root = filepath.Clean(root)
	if err = os.MkdirAll(root, 0755); err != nil {
		return
	}
	return &OsFileRawStorage{root: root, etager: etager, append: append, states: NewStorageStates()}, nil
}

// 设置元数据目录，存储元数据
func (s *OsFileRawStorage) WithMeta(path string) *OsFileRawStorage {
	path = filepath.Clean(path)
	os.MkdirAll(path, 0755)
	s.meta = path
	return s
}

// 获取文件
func (s *OsFileRawStorage) Get(path string) (reader io.ReadCloser, meta Meta, err error) {
	meta, err = s.Meta(path)
	if err != nil {
		return nil, nil, err
	}
	reader, err = os.Open(s.path(path))
	return
}

// 获取文件元信息，当元信息不存在或读取出错时，则从文件中重新读取
func (s *OsFileRawStorage) Meta(path string) (meta Meta, err error) {
	mp := s.metapath(path)
	path = s.path(path)
	if s.states.IsWriting(path) {
		return nil, ErrWriting
	}
	if mp == "" {
		return MetaOfFile(path, s.etager)
	} else if meta, err = MetaOfHttpHeaderTextFile(mp); err == nil {
		return
	}
	return MetaOfFile(path, s.etager)
}

// 获取资源列表
func (s *OsFileRawStorage) List(path string) (*StorageInfo, error) {
	fullpath := s.path(path)
	stat, err := os.Stat(fullpath)
	if err != nil {
		return nil, err
	}
	respath := strings.Trim(filepath.ToSlash(filepath.Clean(path)), "/")
	si := &StorageInfo{Name: respath, IsContainer: stat.IsDir(), Size: stat.Size(), ModTime: stat.ModTime()}
	if !si.IsContainer {
		return si, nil
	}
	dirs, err := os.ReadDir(fullpath)
	if err != nil {
		return nil, err
	}
	si.Items = make([]*StorageInfo, 0, len(dirs))
	for _, de := range dirs {
		it := &StorageInfo{Name: strings.Trim(fmt.Sprintf("%s/%s", respath, de.Name()), "/"), IsContainer: de.IsDir()}
		if ds, err := de.Info(); err == nil {
			it.Size, it.ModTime = ds.Size(), ds.ModTime()
		}
		si.Items = append(si.Items, it)
	}
	return si, nil
}

// 对象的状态，如果状态不存在，则返回 nil
func (s *OsFileRawStorage) State(path string) (*StorageState, error) {
	return s.states.State(s.path(path)), nil
}

// 保存文件。
func (s *OsFileRawStorage) Save(path string, meta Meta, content io.Reader) (err error) {
	mp := s.metapath(path)
	path = s.path(path)
	if s.states.IsWriting(path) {
		return ErrWriting // 此前已经在写入
	}
	s.states.WriteStarted(path)
	defer s.states.Writed(path) // 写入完成
	fd, err := FileCreate(path, s.append)
	if err != nil {
		return
	}
	defer fd.Close()
	if mp == "" { // 无需存储元数据
		_, err = io.Copy(fd, content)
		return
	}
	size := meta.ContentLength()
	meta, err = MetaFix(fd, content, s.etager, meta, time.Now(), filepath.Ext(path))
	if err == nil && size > 0 && meta.ContentLength() != size { // 读取到的内容长度和指定的长度不一致
		return fmt.Errorf("size wrote from reader [%d] is not equals to expected [%d]", meta.ContentLength(), size)
	}
	if err == nil {
		s.saveMeta(mp, meta)
	}
	return
}

// 删除文件
func (s *OsFileRawStorage) Remove(path string) (err error) {
	mp := s.metapath(path)
	path = s.path(path)
	if s.states.IsWriting(path) {
		return ErrWriting
	}
	if mp != "" {
		FileRemove(mp, s.meta)
	}
	return FileRemove(path, s.root)
}

// 重命名文件
func (s *OsFileRawStorage) Rename(oldpath, newpath string) (err error) {
	oldmeta := s.metapath(oldpath)
	newmeta := s.metapath(newpath)
	oldpath = s.path(oldpath)
	newpath = s.path(newpath)
	if s.states.IsWriting(oldpath) || s.states.IsWriting(newpath) {
		return ErrWriting
	}
	if oldmeta != "" && newmeta != "" {
		FileRename(oldmeta, newmeta, s.meta)
	}
	return FileRename(oldpath, newpath, s.root)
}

// 计算文件路径
func (s *OsFileRawStorage) path(path string) string {
	return filepath.Join(s.root, filepath.Clean(path))
}

// 计算元数据文件路径
func (s *OsFileRawStorage) metapath(path string) string {
	if s.meta == "" || path == "" {
		return ""
	}
	return filepath.Join(s.meta, filepath.Clean(path))
}

// 保存元数据
func (s *OsFileRawStorage) saveMeta(path string, meta Meta) error {
	if path == "" {
		return nil
	}
	fd, err := FileCreate(path, false)
	if err != nil {
		return err
	}
	defer fd.Close()
	_, err = fd.WriteString(MetaHttpHeaderText(meta))
	// 保存引用
	return err
}
